/*
    This file is part of Emu.

    Emu is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Emu is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Emu.  If not, see <http://www.gnu.org/licenses/>. */
#include <QApplication>
#include <QClipboard>
#include <QFileDialog>
#include <QFontDialog>
#include <QMenuBar>
#include <QMessageBox>
#include <QScrollBar>

#include "eprofilemanager.h"
#include "eprofilewriter.h"
#include "esessioneditor.h"
#include "emainwindow.h"

EMainWindow::EMainWindow()
{
    _globalPrefs = EGlobalPrefs::loadPrefs();

    _display = new QTabWidget;
    _display->setMovable(true);
    _display->setTabsClosable(true);
    /* The tab having focus isn't useful, so make it so it can't have focus. */
    _display->tabBar()->setFocusPolicy(Qt::NoFocus);

    if (_globalPrefs->viewerSize() != QSize(0, 0)) {
        resize(_globalPrefs->viewerSize());
        move(_globalPrefs->viewerPos());
    }

    createMenus();
    setCentralWidget(_display);
    setWindowIcon(QIcon(":/appicon.png"));
    setWindowTitle(tr("Emu Viewer"));
    setAttribute(Qt::WA_DeleteOnClose);

    _currentSession = NULL;

    QClipboard *clipboard = QApplication::clipboard();

    connect(_display, SIGNAL(tabCloseRequested(int)), this,
            SLOT(closeTab(int)));
    connect(_display, SIGNAL(currentChanged(int)), this,
            SLOT(currentChanged(int)));
    connect(clipboard, SIGNAL(dataChanged()), this,
            SLOT(catchClipboardChanged()));
}

EMainWindow::~EMainWindow()
{
    EGlobalPrefs::savePrefs(_globalPrefs);

    delete _globalPrefs;
}

void EMainWindow::currentChanged(int)
{
    ESession *newCurrent = NULL;
    QWidget *displayCurrent = _display->currentWidget();
    int i;
    for (i = 0;i < _sessions.size();i++) {
        if (_sessions.at(i)->splitter() == displayCurrent) {
            newCurrent = _sessions.at(i);
            break;
        }
    }

    if (newCurrent) {
        if (_currentSession) {
            _globalPrefs->setHandle(_currentSession->splitter()->saveState());
            newCurrent->setCurrent(false);
        }
        newCurrent->setCurrent(true);
        QSplitter *splitter = newCurrent->splitter();
        splitter->restoreState(_globalPrefs->handle());
        _currentSession = newCurrent;

        QScrollBar *sb = _currentSession->recvWindow()->verticalScrollBar();
        /* Don't let focus jump back to the send window unless all data has been
           read. If the user has unread data, swing focus to the top window so
           more data won't push the scrollbar down. */
        if (sb->value() != sb->maximum())
            _currentSession->recvWindow()->setFocus();
        else
            _currentSession->sendWindow()->setFocus();

        /* Gotta make sure this is called, because edit menus will be off if it
           is not triggered. */
        catchFocusChanged();
    }
    else
        _currentSession = NULL;
}

void EMainWindow::closeTab(int index)
{
    ESession *session = NULL;
    QWidget *tabWidget = _display->widget(index);
    int i;
    for (i = 0;i < _sessions.size();i++) {
        if (_sessions.at(i)->splitter() == tabWidget) {
            session = _sessions.at(i);
            break;
        }
    }

    if (promptCloseSession(session) == true) {
        /* Only one session left, so disable prev and next actions. */
        if (_sessions.size() == 2) {
            _prevSessionAct->setEnabled(false);
            _nextSessionAct->setEnabled(false);
        }

        /* The only session is being deleted, so revert to how emu started. */
        if (_sessions.size() == 1) {
            _globalPrefs->setHandle(session->splitter()->saveState());
            setWindowTitle(tr("Emu Viewer"));
            setEditItemsEnabled(false);
        }

        _display->removeTab(_display->indexOf(session->splitter()));

        _sessions.removeOne(session);
        if (_sessions.size() == 0)
            _currentSession = NULL;

        delete session;
    }
}

void EMainWindow::catchClipboardChanged()
/* The clipboard contents have changed, so see if the current window can still
   paste from the clipboard. */
{
    bool enable;

    if (_currentSession &&
        _currentSession->sendWindow()->canPaste() &&
         (_currentSession->sendWindow()->hasFocus() ||
          _currentSession->searchWindow()->hasFocus()))
        enable = true;
    else
        enable = false;

    _pasteAct->setEnabled(enable);
}

void EMainWindow::catchFocusChanged()
/* Focus has gone to a different window. Update the available menu items
   accordingly. */
{
    if (_currentSession->sendWindow()->hasFocus()) {
        /* The text edit knows if it can paste or not. */
        QTextEdit *sendWindow = _currentSession->sendWindow();
        _pasteAct->setEnabled(_currentSession->sendWindow()->canPaste());

        /* The document stores the undo/redo stack */
        QTextDocument *sendDoc = sendWindow->document();
        _redoAct->setEnabled(sendDoc->isRedoAvailable());
        _undoAct->setEnabled(sendDoc->isUndoAvailable());

        /* The cursor stores the currrent selection for cut+copy. */
        QTextCursor sendCursor = sendWindow->textCursor();
        _cutAct->setEnabled(sendCursor.hasSelection());
        _copyAct->setEnabled(sendCursor.hasSelection());
    }
    else if (_currentSession->recvWindow()->hasFocus()) {
        QTextCursor recvCursor = _currentSession->recvWindow()->textCursor();

        _copyAct->setEnabled(recvCursor.hasSelection());
        /* The receive window is read-only, so the rest is never possible. */
        _cutAct->setEnabled(false);
        _pasteAct->setEnabled(false);
        _undoAct->setEnabled(false);
        _redoAct->setEnabled(false);
    }
    else if (_currentSession->searchWindow()->hasFocus()) {
        QLineEdit *searchWindow = _currentSession->searchWindow();

        _cutAct->setEnabled(searchWindow->selectedText().isEmpty() == false);
        _copyAct->setEnabled(searchWindow->selectedText().isEmpty() == false);
        /* Qt provides no way to tell if a QLineEdit can paste so... */
        _pasteAct->setEnabled(_currentSession->sendWindow()->canPaste());
        _redoAct->setEnabled(searchWindow->isRedoAvailable());
        _undoAct->setEnabled(searchWindow->isUndoAvailable());
    }
}

void EMainWindow::closeEvent(QCloseEvent *e)
/* Called when the mainwindow is about to close. Ask to be sure first. */
{
    if (_currentSession) {
        QMessageBox::StandardButton quitAnswer = QMessageBox::warning(0,
                tr("Emu"), tr("Really close all sessions?"),
                QMessageBox::Yes | QMessageBox::No);

        if (quitAnswer == QMessageBox::No) {
            e->ignore();
            return;
        }
        _globalPrefs->setHandle(_currentSession->splitter()->saveState());
    }

    _globalPrefs->setViewerSize(size());
    _globalPrefs->setViewerPos(pos());
    e->accept();
}

void EMainWindow::copy()
{
    if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->copy();
    else if (_currentSession->recvWindow()->hasFocus())
        _currentSession->recvWindow()->copy();
    else
        _currentSession->searchWindow()->copy();
}

void EMainWindow::cut()
{
    if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->cut();
    else
        _currentSession->searchWindow()->cut();
}

void EMainWindow::editSession()
/* Fire up the session edit dialog. */
{
    ESessionEditor sessEditor(_currentSession->profile(), this);
    if (sessEditor.exec() == QDialog::Accepted) {
        if (sessEditor.needReset()) {
            _currentSession->resetAppearance(sessEditor.changes());

            QString err;
            EProfileWriter w;
            w.savePrefs(_currentSession->profile(), &err);

            if (!err.isEmpty())
                _currentSession->writeMessage(err);
            else
                _currentSession->writeMessage(tr("Profile changes saved.\n"));
        }
    }
}

void EMainWindow::findNext()
{
    showSearchBar();
    _currentSession->searchBar()->catchSearchNext();
}

void EMainWindow::findPrevious()
{
    showSearchBar();
    _currentSession->searchBar()->catchSearchPrevious();
}

void EMainWindow::showSearchBar()
{
    _currentSession->searchBar()->show();
    _currentSession->searchWindow()->setFocus();
}

QString EMainWindow::promptToSaveLog(QString savePath, bool prompt)
/* Get a path for the user to save a log to. If prompt is true, then ask the
   user for a path even if one exists. Otherwise, just yield a path. */
{
    EProfile *prefs = _currentSession->profile();

    if (savePath.isEmpty()) {
        savePath = tr("%2_at_%3_port_%4.txt")
                .arg(prefs->hostName())
                .arg(prefs->hostAddress())
                .arg(prefs->hostAddress());
        prompt = true;
    }

    if (prompt) {
        QString filter = tr("Text Files (*.txt)");
        QString title = tr("Save Log");

        /* This will be empty if the dialog is cancelled. The caller checks for
           that before opening the file. */
        savePath = QFileDialog::getSaveFileName(0, title, savePath, filter);
    }

    return savePath;
}

void EMainWindow::nextSession()
{
    QWidget *widget = NULL;
    widget = _display->widget(_display->currentIndex() + 1);
    if (widget == NULL)
        widget = _display->widget(0);

    _display->setCurrentWidget(widget);
}

void EMainWindow::newSession()
/* 'Open Profile' selected from the File menu. */
{
    EProfileManager profileManager(this);
    if (profileManager.exec() == QDialog::Accepted)
        createSession(profileManager.selectedPrefs());
}

void EMainWindow::paste()
/* Paste action. */
{
    if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->paste();
    else
        _currentSession->searchWindow()->paste();
}

void EMainWindow::prevSession()
{
    QWidget *widget = NULL;
    widget = _display->widget(_display->currentIndex() - 1);
    if (widget == NULL)
        widget = _display->widget(_display->count() - 1);

    _display->setCurrentWidget(widget);
}

void EMainWindow::redo()
/* Redo action. */
{
    if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->redo();
    else
        _currentSession->searchWindow()->redo();
}

/* This may show a prompt asking the user if they want to close the session. */
bool EMainWindow::promptCloseSession(ESession *session)
{
    if (session->socketState() == SocketNeverConnected)
        return true;

    QMessageBox::StandardButton closePrompt = QMessageBox::warning(0, tr("Emu"),
        tr("Are you sure you want to close this session?"),
        QMessageBox::Yes | QMessageBox::No);

    return (closePrompt == QMessageBox::Yes);
}

/* Pretend that the close button has been clicked for the current tab, which
   will handle the rest. */
void EMainWindow::quitSession()
{
    closeTab(_display->indexOf(_display->currentWidget()));
}

void EMainWindow::saveLog()
{
    QString s = promptToSaveLog(_currentSession->logfilePath(), false);
    if (!s.isEmpty())
        _currentSession->save(s);
}

void EMainWindow::saveLogAs()
{
    QString s = promptToSaveLog(_currentSession->logfilePath(), true);
    if (!s.isEmpty())
        _currentSession->save(s);
}

void EMainWindow::selectAll()
/* Select All action. */
{
    if (_currentSession->recvWindow()->hasFocus())
        _currentSession->recvWindow()->selectAll();
    else if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->selectAll();
    else
        _currentSession->searchWindow()->selectAll();
}

void EMainWindow::setEditItemsEnabled(bool enabled)
/* This handles toggling the menu items that depend on having at least one
   session. Note that this does not include prev and next session, which depend
   on having 2+ sessions. */
{
    _cutAct->setEnabled(enabled);
    _copyAct->setEnabled(enabled);
    _editSessionAct->setEnabled(enabled);
    _findNextAct->setEnabled(enabled);
    _findPrevAct->setEnabled(enabled);
    _quitSessionAct->setEnabled(enabled);
    _pasteAct->setEnabled(enabled);
    _redoAct->setEnabled(enabled);
    _saveLogAct->setEnabled(enabled);
    _saveLogAsAct->setEnabled(enabled);
    _selectAllAct->setEnabled(enabled);
    _showFindAct->setEnabled(enabled);
    _undoAct->setEnabled(enabled);
}

void EMainWindow::createFileMenu()
{
    QMenu *file = menuBar()->addMenu(tr("File"));

    /* File->New Session */
    QAction *newSessionAct;
    newSessionAct = file->addAction(QIcon(":/icons/new_session.png"),
                                    tr("New Session"), this,
                                    SLOT(newSession()),
                                    QKeySequence::AddTab);
    Q_UNUSED(newSessionAct);

    /* File->Edit Session Profile */
    _editSessionAct = file->addAction(QIcon(":/icons/edit_session.png"),
                                      tr("Edit Session Profile"), this,
                                      SLOT(editSession()),
                                      QKeySequence("Ctrl+E"));

    /* --- */
    file->addSeparator();

    /* File->Save Log */
    _saveLogAct = file->addAction(QIcon(":/icons/save.png"),
                                  tr("Save Log"), this, SLOT(saveLog()),
                                  QKeySequence::Save);

    /* File->Save Log As */
    _saveLogAsAct = file->addAction(QIcon(":/icons/save_as.png"),
                                    tr("Save Log As"), this, SLOT(saveLogAs()),
                                    QKeySequence::SaveAs);

    /* --- */
    file->addSeparator();

    /* File->Quit Session */
    _quitSessionAct = file->addAction(QIcon(":/icons/close_session.png"),
                                      tr("Close Session"), this,
                                      SLOT(quitSession()), QKeySequence::Close);

    /* --- */
    file->addSeparator();

    /* File->Exit */
    QAction *exitAct = file->addAction(QIcon(":/icons/exit.png"), tr("Exit"),
                                       this, SLOT(close()),
                                       QKeySequence("Alt+F4"));
    Q_UNUSED(exitAct);
}

void EMainWindow::createEditMenu()
{
    QMenu *edit = menuBar()->addMenu(tr("Edit"));

    /* Edit->Undo */
    _undoAct = edit->addAction(QIcon(":/icons/undo.png"), tr("Undo"), this,
                               SLOT(undo()), QKeySequence::Undo);

    /* Edit->Redo */
    _redoAct = edit->addAction(QIcon(":/icons/redo.png"), tr("Redo"), this,
                              SLOT(redo()), QKeySequence::Redo);

    /* --- */
    edit->addSeparator();

    /* Edit->Cut */
    _cutAct = edit->addAction(QIcon(":/icons/cut.png"), tr("Cut"), this,
                              SLOT(cut()), QKeySequence::Cut);

    /* Edit->Copy */
    _copyAct = edit->addAction(QIcon(":/icons/copy.png"), tr("Copy"), this,
                               SLOT(copy()), QKeySequence::Copy);

    /* Edit->Paste */
    _pasteAct = edit->addAction(QIcon(":/icons/paste.png"), tr("Paste"), this,
                                SLOT(paste()), QKeySequence::Paste);

    /* Edit->Select All */
    _selectAllAct = edit->addAction(QIcon(":/icons/select_all.png"),
                                    tr("Select All"), this,
                                    SLOT(selectAll()), QKeySequence::SelectAll);

    /* --- */
    edit->addSeparator();

    /* Edit->Global Font */
    _fontAct = edit->addAction(tr("Change Global Font"), this,
                               SLOT(showFontDialog()));
}

void EMainWindow::createSessMenu()
{
    QMenu *sess = menuBar()->addMenu(tr("Session"));

    /* Session->Previous Session */
    _prevSessionAct = sess->addAction(QIcon(":/icons/up.png"),
                                      tr("Previous Session"), this,
                                      SLOT(prevSession()),
                                      QKeySequence::PreviousChild);

    /* Session->Next Session */
    _nextSessionAct = sess->addAction(QIcon(":/icons/down.png"),
                                  tr("Next Session"), this, SLOT(nextSession()),
                                  QKeySequence::NextChild);

    /* --- */
    sess->addSeparator();

    /* Session->Find */
    _showFindAct = sess->addAction(QIcon(":/icons/search.png"), tr("Find"),
                                   this, SLOT(showSearchBar()),
                                   QKeySequence::Find);

    /* Session->Find Next */
    _findNextAct = sess->addAction(tr("Find Next"), this, SLOT(findNext()),
                                   QKeySequence::FindNext);

    /* Session->Find Previous */
    _findPrevAct = sess->addAction(tr("Find Previous"), this,
                                   SLOT(findPrevious()),
                                   QKeySequence::FindPrevious);
}

void EMainWindow::createMenus()
{
    createFileMenu();
    createEditMenu();
    createSessMenu();

    /* Since there are no sessions, disable cut and friends. */
    setEditItemsEnabled(false);
}

void EMainWindow::toggleUndoEnabled(bool available)
{
    _undoAct->setEnabled(available);
}

void EMainWindow::toggleRedoEnabled(bool available)
{
    _redoAct->setEnabled(available);
}

void EMainWindow::toggleCopyEnabled(bool available)
{
    _copyAct->setEnabled(available);
}

void EMainWindow::toggleCutCopyEnabled(bool available)
{
    _copyAct->setEnabled(available);
    _cutAct->setEnabled(available);
}

void EMainWindow::createSession(EProfile *profile)
{
    ESession *newSession = new ESession(profile);
    newSession->splitter()->updateFont(_globalPrefs->font());

    _sessions.append(newSession);

    /* Enable prev and next session, now that there are two sessions. */
    if (_sessions.size() == 2) {
        _prevSessionAct->setEnabled(true);
        _nextSessionAct->setEnabled(true);
    }

    if (_currentSession == NULL) {
        /* The edit items are disabled, so fix that. */
        setEditItemsEnabled(true);

        /* Use the splitter state from last time. */
        newSession->splitter()->restoreState(_globalPrefs->handle());
    }
    else {
        /* A current session exists, so use the state from it. */
        newSession->splitter()->restoreState(_currentSession->splitter()->saveState());
    }

    /* When the current session is changed, the edit menu is updated for the
       current widget. In the meantime, these connections update the edit menu
       for when one or two things become available. */
    connect(newSession->sendWindow(), SIGNAL(undoAvailable(bool)), this,
            SLOT(toggleUndoEnabled(bool)));
    connect(newSession->sendWindow(), SIGNAL(redoAvailable(bool)), this,
            SLOT(toggleRedoEnabled(bool)));
    connect(newSession->sendWindow(), SIGNAL(copyAvailable(bool)), this,
            SLOT(toggleCutCopyEnabled(bool)));

    connect(newSession->recvWindow(), SIGNAL(copyAvailable(bool)), this,
            SLOT(toggleCopyEnabled(bool)));

    /* The search widget is a QLineEdit, so those signals are not available. The
       searchbar implements the signals, with the names reflecting that. */
    connect(newSession->searchBar(), SIGNAL(searchWindowCutCopyAvailable(bool)),
            this, SLOT(toggleCutCopyEnabled(bool)));
    connect(newSession->searchBar(), SIGNAL(searchWindowUndoAvailable(bool)),
            this, SLOT(toggleUndoEnabled(bool)));
    connect(newSession->searchBar(), SIGNAL(searchWindowRedoAvailable(bool)),
            this, SLOT(toggleRedoEnabled(bool)));


    connect(newSession, SIGNAL(focusChanged()), this, SLOT(catchFocusChanged()));

    _display->addTab(newSession->splitter(), newSession->profile()->hostName());
    _display->setCurrentWidget(newSession->splitter());
    newSession->sendWindow()->setFocus();

    /* Now tell the session to connect to the specified host. */
    newSession->start();
}

void EMainWindow::showFontDialog()
{
    QFontDialog fontPicker(_globalPrefs->font(), this);
    if (fontPicker.exec() == QDialog::Accepted) {
        QFont selectedFont = fontPicker.selectedFont();
        _globalPrefs->setFont(selectedFont);
        int i;
        for (i = 0;i < _sessions.size();i++)
            _sessions.at(i)->splitter()->updateFont(selectedFont);
    }
}

void EMainWindow::start()
{
    show();
}

void EMainWindow::undo()
{
    if (_currentSession->sendWindow()->hasFocus())
        _currentSession->sendWindow()->undo();
    else
        _currentSession->searchWindow()->undo();
}
